package com.luofan.common.util;

import com.luofan.common.config.MinioConfig;
import io.minio.*;
import io.minio.errors.ErrorResponseException;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 *  Minio工具类
 */

@Component
public class MinioUtil {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinioConfig configuration;

    /**
     * 判断存储桶是否存在
     * @param name 存储桶名称
     */
    public boolean existBucket(String name) {
        boolean exists;
        try {
            exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
        } catch (Exception e) {
            e.printStackTrace();
            exists = false;
        }
        return exists;
    }

    /**
     * 创建一个存储桶 (默认的访问权限 private)
     * @param bucketName 存储桶名称
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 创建一个只读的存储桶
     * @param bucketName 存储桶名称
     * @return 创建是否成功
     */
    public Boolean makeOnlyReadBucket(String bucketName) {
        try {
            // 创建存储桶
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());

            // 设置存储桶策略为只读
            String policy = "{\"Version\": \"2012-10-17\", \"Statement\": [{\"Effect\": \"Allow\", \"Principal\": \"*\", \"Action\": [\"s3:GetObject\"], \"Resource\": \"arn:aws:s3:::" + bucketName + "/*\"}]}";

            // 设置存储桶的只读策略
            minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
                    .bucket(bucketName)
                    .config(policy)
                    .build());

        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 删除指定的存储桶
     * @param bucketName 存储桶名称
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 列出所有有权访问的存储桶
     * bucket中的属性有 name名称和 creationDate创建时间
     */
    public List<Bucket> listBucket() {
        try {
            return minioClient.listBuckets();
        } catch (Exception e) {
            e.printStackTrace();
            return Collections.emptyList();
        }
    }

    /**
     * 获取POST表单上传临时签名
     * @param fileName 文件名称
     * @param time     过期时间
     */
    @SneakyThrows
    public Map getPolicy(String fileName, ZonedDateTime time) {
        PostPolicy postPolicy = new PostPolicy(configuration.getBucketName(), time);
        postPolicy.addEqualsCondition("key", fileName);
        try {
            Map<String, String> map = minioClient.getPresignedPostFormData(postPolicy);
            HashMap<String, String> map1 = new HashMap<>();
            map.forEach((k, v) -> {
                map1.put(k.replaceAll("-", ""), v);
            });
            map1.put("host", configuration.getEndpoint() + "/" + configuration.getBucketName());
            return map1;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 上传文件到存储桶中
     * @param file     文件
     * @param storedFileName 文件名
     */
    public Boolean upload(MultipartFile file, String storedFileName) {  // storedFileName是UUID生成的文件名
        try (InputStream inputStream = file.getInputStream()) {
            // 获取原始文件名
            String originalFileName = file.getOriginalFilename();
            if (originalFileName == null || originalFileName.isEmpty()) {
                originalFileName = "unknown";  // 默认值
            }

            // 上传到 Minio，并将原始文件名存入元数据
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(configuration.getBucketName())
                            .object(storedFileName)  // 存储时使用 UUID 文件名
                            .stream(inputStream, file.getSize(), -1)
                            .contentType(file.getContentType())
                            .userMetadata(Map.of(
                                    "original-filename", originalFileName  // 元数据键值对
                            ))
                            .build()
            );
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /*
    public Boolean upload(MultipartFile file, String fileName) {
        try {
            InputStream inputStream = file.getInputStream();
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(configuration.getBucketName())  //指定上传的桶的名称
                    .object(fileName)
                    .stream(inputStream, file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
     */

    /**
     * 获取文件访问地址url
     * @param objectName 对象名称
     */
    public String getUrl(String objectName) {
        String url = null;
        try {
            url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(configuration.getBucketName())
                    .object(objectName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return url;
    }

    /**
     * 获取文件临时访问地址url
     * @param objectName 对象名称
     * @param time       有效时间
     * @param timeUnit   时间单位
     */
    public String getUrl(String objectName, int time, TimeUnit timeUnit) {
        String url = null;
        try {
            url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(configuration.getBucketName())
                    .object(objectName)
                    .expiry(time, timeUnit)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return url;
    }

    /**
     * 下载文件
     * @param storedFileName 需要下载的文件名称
     */
    public ResponseEntity<StreamingResponseBody> download(String storedFileName) {  // storedFileName是UUID文件名
        try {
            //获取对象元数据（包含原始文件名）
            StatObjectResponse stat = minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(configuration.getBucketName())
                            .object(storedFileName)
                            .build()
            );

            //从元数据中提取原始文件名（若不存在则使用 UUID）
            String originalFileName = stat.userMetadata().getOrDefault(
                    "original-filename", storedFileName
            );

            //编码文件名（符合 RFC 5987）
//            String encodedFileName = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8)
//                    .replace("+", "%20");

            //设置响应头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentDisposition(ContentDisposition.attachment()
                    .filename(originalFileName, StandardCharsets.UTF_8)
                    .build());
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

            //流式传输文件内容
            InputStream in = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(configuration.getBucketName())
                            .object(storedFileName)
                            .build()
            );

            StreamingResponseBody body = outputStream -> {
                try (in) {
                    IOUtils.copy(in, outputStream);
                }
            };

            return new ResponseEntity<>(body, headers, HttpStatus.OK);

        } catch (ErrorResponseException e) {
            if (Objects.equals(e.errorResponse().code(), "404")) {
                return ResponseEntity.notFound().build();
            }
            return ResponseEntity.internalServerError().build();
        } catch (Exception e) {
            return ResponseEntity.internalServerError().build();
        }
    }
    /*
    public ResponseEntity<byte[]> download(String fileName) {
        ResponseEntity<byte[]> responseEntity = null;
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = minioClient.getObject(GetObjectArgs.builder().bucket(configuration.getBucketName()).object(fileName).build());
            out = new ByteArrayOutputStream();
            IOUtils.copy(in, out);
            //封装返回值
            byte[] bytes = out.toByteArray();
            HttpHeaders headers = new HttpHeaders();
            try {
                headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            headers.setContentLength(bytes.length);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setAccessControlExposeHeaders(Arrays.asList("*"));
            responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseEntity;
    }
    */

    /**
     * 删除指定的文件
     * @param fileName 文件名
     */
    public Boolean removeFile(String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(configuration.getBucketName())
                    .object(fileName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 获取存储桶内的所有文件数据
     */
    public Iterable<Result<Item>> listFile() {
        return minioClient.listObjects(ListObjectsArgs.builder()
                .bucket(configuration.getBucketName())
                .build());
    }

}
